From de0f60410fbe4b17accab7e9889935596dea6be1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 28 Aug 2014 13:22:36 -0700 Subject: [PATCH] Turn --git on by default for `cargo-new`. This adds a command-line --no-git option to disable this behavior, as well as adding a global config section for `git = false`. While I was at it I write some documentation for the configuration format that cargo uses. --- src/bin/new.rs | 7 ++- src/cargo/ops/cargo_new.rs | 77 ++++++++++++++++++++++++++----- src/cargo/util/config.rs | 26 ++++++++--- src/doc/source/config.md | 69 +++++++++++++++++++++++++++ src/doc/source/guide.md | 6 ++- src/doc/source/index.md | 5 +- src/doc/source/layouts/layout.erb | 11 +++-- tests/test_cargo_new.rs | 46 ++++++++++++++++-- 8 files changed, 216 insertions(+), 31 deletions(-) create mode 100644 src/doc/source/config.md diff --git a/src/bin/new.rs b/src/bin/new.rs index a1e72c1bd..c2e56945a 100644 --- a/src/bin/new.rs +++ b/src/bin/new.rs @@ -14,7 +14,9 @@ Usage: Options: -h, --help Print this message - --git Initialize a new git repository with a .gitignore + --no-git Don't initialize a new git repository + --git Initialize a new git repository, overriding a + global `git = false` configuration --bin Use a binary instead of a library template -v, --verbose Use verbose output ") @@ -23,9 +25,10 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult debug!("executing; cmd=cargo-new; args={}", os::args()); shell.set_verbose(options.flag_verbose); - let Options { flag_git, flag_bin, arg_path, .. } = options; + let Options { flag_no_git, flag_bin, arg_path, flag_git, .. } = options; let opts = ops::NewOptions { + no_git: flag_no_git, git: flag_git, path: arg_path.as_slice(), bin: flag_bin, diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index 9ff86e79b..85a60c5ae 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -3,15 +3,22 @@ use std::io::{mod, fs, File}; use git2::{Repository, Config}; -use util::{CargoResult, human, ChainError}; +use util::{CargoResult, human, ChainError, config, internal}; use core::shell::MultiShell; pub struct NewOptions<'a> { + pub no_git: bool, pub git: bool, pub bin: bool, pub path: &'a str, } +struct CargoNewConfig { + name: Option, + email: Option, + git: Option, +} + pub fn new(opts: NewOptions, _shell: &mut MultiShell) -> CargoResult<()> { let path = os::getcwd().join(opts.path); if path.exists() { @@ -26,19 +33,29 @@ pub fn new(opts: NewOptions, _shell: &mut MultiShell) -> CargoResult<()> { } fn mk(path: &Path, name: &str, opts: &NewOptions) -> CargoResult<()> { - - if opts.git { + let cfg = try!(global_config()); + if !opts.git && (opts.no_git || cfg.git == Some(false)) { + try!(fs::mkdir(path, io::UserRWX)); + } else { try!(Repository::init(path)); let mut gitignore = "/target\n".to_string(); if !opts.bin { gitignore.push_str("/Cargo.lock\n"); } try!(File::create(&path.join(".gitignore")).write(gitignore.as_bytes())); - } else { - try!(fs::mkdir(path, io::UserRWX)); } - let author = try!(discover_author()); + let (author_name, email) = try!(discover_author()); + // Hoo boy, sure glad we've got exhaustivenes checking behind us. + let author = match (cfg.name, cfg.email, author_name, email) { + (Some(name), Some(email), _, _) | + (Some(name), None, _, Some(email)) | + (None, Some(email), name, _) | + (None, None, name, Some(email)) => format!("{} <{}>", name, email), + (Some(name), None, _, None) | + (None, None, name, None) => name, + }; + try!(File::create(&path.join("Cargo.toml")).write_str(format!( r#"[package] @@ -66,7 +83,7 @@ fn it_works() { Ok(()) } -fn discover_author() -> CargoResult { +fn discover_author() -> CargoResult<(String, Option)> { let git_config = Config::open_default().ok(); let git_config = git_config.as_ref(); let name = git_config.and_then(|g| g.get_str("user.name").ok()) @@ -82,8 +99,46 @@ fn discover_author() -> CargoResult { let name = name.as_slice().trim().to_string(); let email = email.map(|s| s.as_slice().trim().to_string()); - Ok(match (name, email) { - (name, Some(email)) => format!("{} <{}>", name, email), - (name, None) => name, - }) + Ok((name, email)) +} + +fn global_config() -> CargoResult { + let user_configs = try!(config::all_configs(os::getcwd())); + let mut cfg = CargoNewConfig { + name: None, + email: None, + git: None, + }; + let cargo_new = match user_configs.find_equiv(&"cargo-new") { + None => return Ok(cfg), + Some(target) => try!(target.table().chain_error(|| { + internal("invalid configuration for the key `cargo-new`") + })), + }; + cfg.name = match cargo_new.find_equiv(&"name") { + None => None, + Some(name) => { + Some(try!(name.string().chain_error(|| { + internal("invalid configuration for key `cargo-new.name`") + })).ref0().to_string()) + } + }; + cfg.email = match cargo_new.find_equiv(&"email") { + None => None, + Some(email) => { + Some(try!(email.string().chain_error(|| { + internal("invalid configuration for key `cargo-new.email`") + })).ref0().to_string()) + } + }; + cfg.git = match cargo_new.find_equiv(&"git") { + None => None, + Some(git) => { + Some(try!(git.boolean().chain_error(|| { + internal("invalid configuration for key `cargo-new.git`") + })).val0()) + } + }; + + Ok(cfg) } diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 2747c8f8a..cf1cf419e 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -81,6 +81,7 @@ pub enum ConfigValue { String(String, Path), List(Vec<(String, Path)>), Table(HashMap), + Boolean(bool, Path), } impl fmt::Show for ConfigValue { @@ -98,6 +99,7 @@ impl fmt::Show for ConfigValue { write!(f, "]") } Table(ref table) => write!(f, "{}", table), + Boolean(b, ref path) => write!(f, "{} (from {})", b, path.display()), } } } @@ -111,6 +113,7 @@ impl> Encodable for ConfigValue { list.encode(s) } Table(ref table) => table.encode(s), + Boolean(b, _) => b.encode(s), } } } @@ -119,6 +122,7 @@ impl ConfigValue { fn from_toml(path: &Path, toml: toml::Value) -> CargoResult { match toml { toml::String(val) => Ok(String(val, path.clone())), + toml::Boolean(b) => Ok(Boolean(b, path.clone())), toml::Array(val) => { Ok(List(try!(result::collect(val.move_iter().map(|toml| { match toml { @@ -140,6 +144,7 @@ impl ConfigValue { fn merge(&mut self, from: ConfigValue) -> CargoResult<()> { match (self, from) { (me @ &String(..), from @ String(..)) => *me = from, + (me @ &Boolean(..), from @ Boolean(..)) => *me = from, (&List(ref mut old), List(ref mut new)) => { let new = mem::replace(new, Vec::new()); old.extend(new.move_iter()); @@ -165,25 +170,33 @@ impl ConfigValue { pub fn string(&self) -> CargoResult<(&str, &Path)> { match *self { - Table(..) => Err(internal("expected a string, but found a table")), - List(..) => Err(internal("expected a string, but found a list")), String(ref s, ref p) => Ok((s.as_slice(), p)), + _ => Err(internal(format!("expected a string, but found a {}", + self.desc()))), } } pub fn table(&self) -> CargoResult<&HashMap> { match *self { - String(..) => Err(internal("expected a table, but found a string")), - List(..) => Err(internal("expected a table, but found a list")), Table(ref table) => Ok(table), + _ => Err(internal(format!("expected a table, but found a {}", + self.desc()))), } } pub fn list(&self) -> CargoResult<&[(String, Path)]> { match *self { - String(..) => Err(internal("expected a list, but found a string")), - Table(..) => Err(internal("expected a list, but found a table")), List(ref list) => Ok(list.as_slice()), + _ => Err(internal(format!("expected a list, but found a {}", + self.desc()))), + } + } + + pub fn boolean(&self) -> CargoResult<(bool, &Path)> { + match *self { + Boolean(b, ref p) => Ok((b, p)), + _ => Err(internal(format!("expected a bool, but found a {}", + self.desc()))), } } @@ -192,6 +205,7 @@ impl ConfigValue { Table(..) => "table", List(..) => "array", String(..) => "string", + Boolean(..) => "boolean", } } } diff --git a/src/doc/source/config.md b/src/doc/source/config.md new file mode 100644 index 000000000..efc0befc3 --- /dev/null +++ b/src/doc/source/config.md @@ -0,0 +1,69 @@ +--- +title: Configuration +--- + +This document will explain how cargo's configuration system works, as well as +available keys or configuration. For configuration of a project through its +manfest, see the [manifest format](manifest.html). + +# Hierarchical structure + +Cargo allows to have local configuration for a particular project or global +configuration (like git). Cargo also extends this ability to a hirearchical +strategy. If, for example, cargo were invoked in `/home/foo/bar/baz`, then the +following configuration files would be probed for: + +* `/home/foo/bar/baz/.cargo/config` +* `/home/foo/bar/.cargo/config` +* `/home/foo/.cargo/config` +* `/home/.cargo/config` +* `/.cargo/config` + +With this structure you can specify local configuration per-project, and even +possibly check it into version control. You can also specify personal default +with a configuration file in your home directory. + +# Configuration Format + +All configuration is currently in the TOML format (like the manifest), with +simple key-value pairs inside of sections (tables) which all get merged +together. + +# Configuration keys + +All of the following keys are optional, and their defaults are listed as their +value unless otherwise noted. + +```toml +# An array of paths to local repositories which are to be used as overrides for +# dependencies. For more information see the Cargo Guide. +paths = [ "/path/to/override" ] + +[cargo-new] +# This is your name/email to place in the `authors` section of a new Cargo.toml +# that is generated. If not present, then `git` will be probed, and if that is +# not present then `$USER` will be used (with no email). +name = "..." +email = "..." + +# By default `cargo new` will initialize a new git repository. This key can be +# set to `false` to disable this behavior. +git = true + +# For the following sections, $triple refers to any valid target triple, not the +# literal string "$triple", and it will apply whenever that target triple is +# being compiled to. +[target] + +# For cargo builds which do not mention --target, these are the ar/linker which +# are passed to rustc to use (via `-C ar=` and `-C linker=`). By default these +# flags are not passed to the compiler. +ar = ".." +linker = ".." + +[target.$triple] +# Similar to the above ar/linker configuration, but this only applies to when +# the `$triple` is being compiled for. +ar = ".." +linker = ".." +``` diff --git a/src/doc/source/guide.md b/src/doc/source/guide.md index cced5cfaf..a57df78b4 100644 --- a/src/doc/source/guide.md +++ b/src/doc/source/guide.md @@ -34,7 +34,8 @@ $ cargo new hello_world --bin ``` We're passing `--bin` because we're making a binary program: if we -were making a library, we'd leave it off. +were making a library, we'd leave it off. If you'd like to not initialize a new +git repository as well (the default), you can also pass `--no-git`. Let's check out what Cargo has generated for us: @@ -359,6 +360,9 @@ This array should be filled with directories that contain a `Cargo.toml`. In this instance, we're just adding `conduit`, so it will be the only one that's overridden. +More information about local configuration can be found in the [configuration +documentation](config.html). + # Tests Cargo can run your tests with the `cargo test` command. Cargo runs tests in two diff --git a/src/doc/source/index.md b/src/doc/source/index.md index f5a6eaa71..cbc627326 100644 --- a/src/doc/source/index.md +++ b/src/doc/source/index.md @@ -24,12 +24,11 @@ Alternatively, you can build Cargo from source. To start a new project with Cargo, use `cargo new`: ```shell -$ cargo new hello_world --bin --git +$ cargo new hello_world --bin ``` We're passing `--bin` because we're making a binary program: if we -were making a library, we'd leave it off. We also pass `--git` to auto-generate -a `.gitignore` and set up the git repository, but nothing will be committed. +were making a library, we'd leave it off. Let's check out what Cargo has generated for us: diff --git a/src/doc/source/layouts/layout.erb b/src/doc/source/layouts/layout.erb index 4c6a8d983..e1555473b 100644 --- a/src/doc/source/layouts/layout.erb +++ b/src/doc/source/layouts/layout.erb @@ -2,17 +2,17 @@ - + - + <%= current_page.data.title || "The Middleman" %> - + <%= stylesheet_link_tag "all" %> <%= javascript_include_tag "all" %> - + <%= link_to image_tag("forkme.png", class: "fork-me"), "https://github.com/rust-lang/cargo" %> <%= link_to image_tag("Cargo-Logo-Small.png", class: "logo"), "index.html" %> @@ -26,7 +26,8 @@ <%= link_to "Guide", "guide.html" %> | <%= link_to "Frequently Asked Questions", "faq.html" %> | <%= link_to "Manifest Format", "manifest.html" %> | - <%= link_to "Building Non-Rust Code", "native-build.html" %> + <%= link_to "Building Non-Rust Code", "native-build.html" %> | + <%= link_to "Configuration", "config.html" %> diff --git a/tests/test_cargo_new.rs b/tests/test_cargo_new.rs index a60f7a712..dd4696493 100644 --- a/tests/test_cargo_new.rs +++ b/tests/test_cargo_new.rs @@ -2,7 +2,7 @@ use std::io::{fs, UserRWX, File}; use std::os; use support::{execs, paths, cargo_dir, ResultTest}; -use hamcrest::{assert_that, existing_file, existing_dir}; +use hamcrest::{assert_that, existing_file, existing_dir, is_not}; use cargo::util::{process, ProcessBuilder}; @@ -23,12 +23,13 @@ fn cargo_process(s: &str) -> ProcessBuilder { test!(simple_lib { os::setenv("USER", "foo"); - assert_that(cargo_process("new").arg("foo"), + assert_that(cargo_process("new").arg("foo").arg("--no-git"), execs().with_status(0)); assert_that(&paths::root().join("foo"), existing_dir()); assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); assert_that(&paths::root().join("foo/src/lib.rs"), existing_file()); + assert_that(&paths::root().join("foo/.gitignore"), is_not(existing_file())); assert_that(cargo_process("build").cwd(paths::root().join("foo")), execs().with_status(0)); @@ -52,7 +53,7 @@ test!(simple_bin { test!(simple_git { os::setenv("USER", "foo"); - assert_that(cargo_process("new").arg("foo").arg("--git"), + assert_that(cargo_process("new").arg("foo"), execs().with_status(0)); assert_that(&paths::root().join("foo"), existing_dir()); @@ -105,3 +106,42 @@ test!(finds_author_git { let toml = File::open(&toml).read_to_string().assert(); assert!(toml.as_slice().contains(r#"authors = ["bar "]"#)); }) + +test!(author_prefers_cargo { + my_process("git").args(["config", "--global", "user.name", "bar"]) + .exec().assert(); + my_process("git").args(["config", "--global", "user.email", "baz"]) + .exec().assert(); + let root = paths::root(); + fs::mkdir(&root.join(".cargo"), UserRWX).assert(); + File::create(&root.join(".cargo/config")).write_str(r#" + [cargo-new] + name = "new-foo" + email = "new-bar" + git = false + "#).assert(); + + assert_that(cargo_process("new").arg("foo").env("USER", Some("foo")), + execs().with_status(0)); + + let toml = paths::root().join("foo/Cargo.toml"); + let toml = File::open(&toml).read_to_string().assert(); + assert!(toml.as_slice().contains(r#"authors = ["new-foo "]"#)); + assert!(!root.join("foo/.gitignore").exists()); +}) + +test!(git_prefers_command_line { + let root = paths::root(); + fs::mkdir(&root.join(".cargo"), UserRWX).assert(); + File::create(&root.join(".cargo/config")).write_str(r#" + [cargo-new] + git = false + name = "foo" + email = "bar" + "#).assert(); + + assert_that(cargo_process("new").arg("foo").arg("--git") + .env("USER", Some("foo")), + execs().with_status(0)); + assert!(root.join("foo/.gitignore").exists()); +}) -- 2.30.2